package com.witmore.neutron.ai.cognitive.service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.witmore.neutron.ai.cognitive.dto.SessionDeductionRequest;
import com.witmore.neutron.ai.cognitive.exception.SessionUsageException;
import com.witmore.neutron.ai.db.entity.*;
import com.witmore.neutron.ai.db.mapper.UserKeyMapper;
import com.witmore.neutron.ai.db.mapper.UserMapper;
import com.witmore.neutron.ai.db.mapper.UserUsageDetailMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

/**
 * Description: UserSessionUsageManager
 * Author: 豆子高
 * Date: 2025/9/8 14:47
 */
@Slf4j
@Service
public class UserSessionUsageManager {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserKeyMapper userKeyMapper;

    @Autowired
    private UserUsageDetailMapper userUsageDetailMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 原子执行检查和扣减用户会话次数的逻辑（在锁保护下执行）
     * 包含：1.查询用户 2.检查余额 3.扣减余额 4.记录明细
     *
     * @param request 扣减请求参数
     * @throws SessionUsageException 当用户不存在、余额不足或扣减失败时抛出
     */
    @Transactional(rollbackFor = Exception.class)
    public void doDeductUserSessionCountAtomic(SessionDeductionRequest request) {
        try {
            Integer userId = request.getUserId();
            ModelConfigDO modelConfigDO = request.getModelConfigDO();
            String sessionId = request.getSessionId();
            Integer agentId = request.getAgentId();
            Object toolCallbacks = request.getToolCallbacks();
            String userApiKey = request.getUserApiKey();

            // 1. 查询用户信息
            UserDO user = userMapper.selectById(userId);
            if (user == null) {
                log.error("用户不存在，userId: {}", userId);
                throw new SessionUsageException.UserNotFoundException(userId);
            }

            // 2. 获取模型每次对话消耗的会话次数
            Integer sessionCostCount = modelConfigDO.getSessionCostCount();
            if (sessionCostCount == null || sessionCostCount <= 0) {
                sessionCostCount = 1; // 默认消耗1次
                log.warn("模型 {} 的 sessionCostCount 为空或无效，使用默认值1", modelConfigDO.getModelName());
            }

            // 3. 检查用户剩余会话次数是否足够
            Integer remainingTimes = user.getSessionRemainingTimes();
            if (remainingTimes == null) {
                remainingTimes = 0;
            }

            if (remainingTimes < sessionCostCount) {
                log.warn("用户 {} 剩余会话次数不足，当前剩余: {}, 需要消耗: {}",
                        userId, remainingTimes, sessionCostCount);
                throw new SessionUsageException.InsufficientSessionException(userId, remainingTimes, sessionCostCount);
            }

            // 4. 扣减用户剩余会话次数
            user.setSessionRemainingTimes(remainingTimes - sessionCostCount);
            user.setUpdateTime(LocalDateTime.now());

            int updateResult = userMapper.updateById(user);
            if (updateResult <= 0) {
                log.error("更新用户会话次数失败，userId: {}", userId);
                throw new SessionUsageException.DeductionFailedException(userId, remainingTimes,
                        new RuntimeException("数据库更新失败"));
            }

            // 5. 记录使用明细
            UserUsageDetailDO usageDetail = new UserUsageDetailDO();
            usageDetail.setDatetime(LocalDateTime.now());
            usageDetail.setUserId(userId);
            usageDetail.setUsageCount(sessionCostCount);

            // 设置用户API密钥
            if (userApiKey != null && !userApiKey.trim().isEmpty()) {
                usageDetail.setUserApiKey(userApiKey);
            }

            // 设置智能助手ID
            usageDetail.setAgentId(agentId);

            // 设置服务商ID（如果有的话）
            usageDetail.setProviderId(request.getProviderId());

            // 设置Token统计信息（如果有的话）
            usageDetail.setUsageTokensIn(request.getUsageTokensIn());
            usageDetail.setUsageTokensOut(request.getUsageTokensOut());
            usageDetail.setUsageTotalTokens(request.getUsageTotalTokens());

            // 处理插件信息 - 将toolCallbacks转换为插件JSON字符串
            usageDetail.setPluginJson(ObjectUtils.isEmpty(toolCallbacks) ? null : JSONObject.toJSONString(toolCallbacks));

            usageDetail.setCreateTime(LocalDateTime.now());
            usageDetail.setUpdateTime(LocalDateTime.now());

            // 获取会话信息
            AgentSessionRecordDetailDO sessionDetail = getSessionDetail(request.getRowMetadataId());
            if (sessionDetail != null) {
                usageDetail.setSessionRecordDetailId(sessionDetail.getId());
                usageDetail.setSessionId(sessionDetail.getSessionId());
                usageDetail.setRowMetadataId(sessionDetail.getRowMetadataId());
            }
            int insertResult = userUsageDetailMapper.insert(usageDetail);
            if (insertResult <= 0) {
                log.error("插入用户使用明细失败，userId: {}", userId);
                // 注意：这里会触发事务回滚
                throw new SessionUsageException.DeductionFailedException(userId, user.getSessionRemainingTimes(),
                        new RuntimeException("插入用户使用明细失败"));
            }

            // 6.更新apiKey使用时间
            if (StringUtils.hasText(userApiKey)) {
                userKeyMapper.update(new UserKeyDO().setLastUsedTime(LocalDateTime.now()),
                        new LambdaQueryWrapper<UserKeyDO>()
                                .eq(UserKeyDO::getApiKey, userApiKey)
                );
            }
            log.info("用户 {} 会话次数扣减成功，消耗次数: {}, 剩余次数: {}, 模型: {}, 会话ID: {}, AgentID: {}",
                    userId, sessionCostCount, user.getSessionRemainingTimes(),
                    modelConfigDO.getModelName(), sessionId, agentId);

        } catch (SessionUsageException e) {
            // 重新抛出业务异常
            throw e;
        } catch (Exception e) {
            log.error("扣减用户会话次数异常，request: {}", request, e);
            throw new SessionUsageException.DeductionFailedException(request.getUserId(), 0, e);
        }
    }

    private AgentSessionRecordDetailDO getSessionDetail(String rowMetadataId) {
        // 根据rowMetadataId从Redis缓存中获取AgentSessionRecordDetailDO数据
        if (StringUtils.hasText(rowMetadataId)) {
            String cacheKey = "session_record_detail:" + rowMetadataId;
            log.info("从Redis缓存中获取到会话记录详情，key: {}", cacheKey);
            try {
                String cachedJson = redisTemplate.opsForValue().get(cacheKey);
                if (StringUtils.hasText(cachedJson)) {
                    return JSONObject.parseObject(cachedJson, AgentSessionRecordDetailDO.class);

                }
            } catch (Exception e) {
                log.error("从Redis缓存中获取会话记录详情失败，key: {}", cacheKey, e);
            }
        }
        return null;
    }

    /**
     * 检查用户是否有足够的会话次数
     *
     * @param userId           用户ID
     * @param sessionCostCount 需要消耗的次数
     * @return 是否有足够次数
     */
    public boolean checkUserSessionCount(Integer userId, Integer sessionCostCount) {
        try {
            UserDO user = userMapper.selectById(userId);
            if (user == null) {
                log.error("用户不存在，userId: {}", userId);
                return false;
            }

            Integer remainingTimes = user.getSessionRemainingTimes();
            if (remainingTimes == null) {
                remainingTimes = 0;
            }

            if (sessionCostCount == null || sessionCostCount <= 0) {
                sessionCostCount = 1;
            }

            boolean hasEnough = remainingTimes >= sessionCostCount;
            log.debug("用户 {} 会话次数检查，剩余: {}, 需要: {}, 结果: {}",
                    userId, remainingTimes, sessionCostCount, hasEnough);

            return hasEnough;

        } catch (Exception e) {
            log.error("检查用户会话次数异常，userId: {}", userId, e);
            return false;
        }
    }
}
